// Copyright 2010 Abhishek Kulkarni. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package goprop

import (
	"reflect"
)

/*
 Propagators are the computational units of a propagator network. They are
 asynchronous, autonomous and stateless machines which operate on values
 stored in cells. Propagators are alerted by cells when there is new
 information to be computed, or propagated to other parts of the network.

 Propagators keep a record of its input and output cells and a function to be
 executed when new information arrives.
*/

// Propagators
type Propagator struct {
	Name     string
	Network  *PropagatorNetwork
	SChan	 chan Msg
	State    States
	Function GenericFn
	Inputs   []*Cell
	Outputs  []*Cell
}

// make the propagator listen for commands
func (p *Propagator) Listen() {
	for {
		msg := <-p.SChan
		if msg.Cmd == Stop {
			p.State = Stopped
			return
		}
		go func() {
			switch msg.Cmd {
			default:
				defer Error("unknown cmd %d\n", msg.Cmd)

			case Run:
				p.Run()
			}
		}()
	}
}

// run a propagator and check if there is new information.
//	if any of the inputs to a propagator is "nil", the propagator suspends
//	itself, otherwise it applies the propagator function on the input values
func (p *Propagator) Run() {
	lenp := len(p.Inputs)
	in := make([]reflect.Value, lenp)

	p.State = Running
	Debug(dbgPropagator, ">> running propagator %s\n", p.Name)
	if lenp > 0 {
		for i := range p.Inputs {
			value := p.Inputs[i].Content()
			Debug(dbgPropagator, ">> %s: input %s is %#v\n", p.Name, p.Inputs[i].Name, value)
			if value == nil {
				p.State = Idle
				return
			} else {
				in[i] = reflect.ValueOf(value)
			}
		}
	}

	p.Execute(in)
}

// execute a propagator
func (p *Propagator) Execute(in []reflect.Value) {
	// get the specific function depending on the type of the argument
	var f interface{}
	if len(in) > 0 {
		f = p.Function(in[0])
	} else {
		f = p.Function(reflect.ValueOf(nil))
	}

	// call the specific function with the proper arguments
	fn := reflect.ValueOf(f)
	out := fn.Call(in)

	p.State = Idle
	if len(out) > 0 {
		for i := range out {
			Debug(dbgPropagator, ">> execution of %s yielded %#v\n", p.Name, out[i].Interface())
			p.Outputs[i].Content(out[i].Interface())
		}
	}
}

// alert a propagator (to run)
func (p *Propagator) Alert() {
	p.SChan <- Msg{Cmd: Run}
}

// stop a propagator
func (p *Propagator) Destroy() {
	if p.State != Stopped {
		p.SChan <- Msg{Cmd: Stop}
	}
}
